#include "stdafx.h"
#include "otpdb.h"
#include "ddm.h"
#include <time.h>
#include <stdio.h>
#include "../CommonFunc/CommonFunc.h"

namespace DDM
{

#define MAXTEXTLEN 4096

#define OTPDB_ERROR(err) (otpdb_errno = int(err), -err)
//-----------------------------------------------------------------------------
OtpDB::OtpDB(int idx) : idx(idx)
{
    db = NULL;
    otpdb_errno = ERR_NO;
    dbPara.dbType = -1;
}
OtpDB* OtpDB::get_instance(int idx)
{
    static OtpDB *instance[4] = {NULL};

    if (instance[idx]) return instance[idx];
    instance[idx] = new OtpDB(idx);
    return instance[idx];
}
DDM::Database *OtpDB::GetOtpDB()
{
    if (!db)
    {
        if (this->dbPara.dbType < 0)
        {
            printk("Error OtpDB Type[%d]!\n", this->dbPara.dbType);
            return NULL;
        }

        char dbname[0x40];
        sprintf(dbname, "%s%d", OTPDB_NAME, idx);
        db = DDM::GetDB(dbname, (DDM::DB_TYPE)this->dbPara.dbType);
    }
    return db;
}
int OtpDB::Connect(OtpDBPara &otpDBPara)
{
    if (this->IsOtpDbConnected())
    {
        if (this->dbPara.dbType != otpDBPara.dbType 
            || strcmp(this->dbPara.user, otpDBPara.user)
            || strcmp(this->dbPara.host, otpDBPara.host)
            || strcmp(this->dbPara.passwd, otpDBPara.passwd)
            || strcmp(this->dbPara.dbname, otpDBPara.dbname))
        {
            this->Close();
        }
        else
        {
            return 0;
        }
    }
    this->dbPara = otpDBPara; 

    DDM::Database *db = this->GetOtpDB();

    if (db->connect(dbPara.host, 
                    dbPara.user, 
                    dbPara.passwd, 
                    dbPara.dbname) < 0) {
        return OTPDB_ERROR(ERR_DB);
    }
    return 0;
}
bool OtpDB::IsOtpDbConnected(void)
{
    if (db && db->IsConnected())
        return true;
    return false;
}
//-----------------------------------------------------------------------------
void OtpDB::Close()
{
    DDM::CloseDB(OTPDB_NAME);
    db = NULL;
}

//-----------------------------------------------------------------------------
int OtpDB::do_get_mid(const char* SN)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, "SELECT ID FROM module_info WHERE SN = '%s'", SN);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0) {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_NOINFO);
    }
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    int mid = (int)col->data;

    db->free_rows(&records);
    return mid;
}

int OtpDB::UpdateModuleID(const char* SN)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, "INSERT INTO module_info (SN) values('%s')", SN);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);

    return OTPDB_ERROR(ERR_NO);
}

int OtpDB::GetModuleID(const char* SN, bool force)
{
    int module_id = -1;

    if ((module_id = do_get_mid(SN)) < 0 && force) {
        if (UpdateModuleID(SN) < 0) return OTPDB_ERROR(ERR_DB);
        if ((module_id = do_get_mid(SN)) < 0) return OTPDB_ERROR(ERR_DB);
    }

    return module_id;
}
//-----------------------------------------------------------------------------
int OtpDB::get_sid_by_sn(const char* sn, char *sid, int max_len)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, "SELECT sensor_id FROM module_info WHERE SN = '%s'", sn);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0) {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    int len = min(max_len-1, (int)col->len);
    memcpy(sid, col->data, len);
    sid[len] = '\0';

	db->free_rows(&records);
    return len;
}
int OtpDB::update_sid(const char *sn, const char *SID)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, "UPDATE module_info SET sensor_id = '%s' WHERE SN = '%s'", SID, sn);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);

    return OTPDB_ERROR(ERR_NO);
}
int OtpDB::get_sn_by_sid(const char* SID, char *SN, int max_len)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, 
                "SELECT SN FROM module_info WHERE sensor_id = '%s' "
                "ORDER BY id DESC LIMIT 1",
                SID);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0)
    {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    int len = min(max_len-1, (int)col->len);
    memcpy(SN, col->data, len);
    SN[len] = '\0';

	db->free_rows(&records);
    return len;
}
//-----------------------------------------------------------------------------
int OtpDB::GetOtpByType(int mid, int type, char *otp, int max_len, time_t *ptm)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, 
                "SELECT otp, update_time FROM otp_base WHERE module_id = %d AND otp_type = %d "
                "ORDER BY update_time DESC LIMIT 1",  mid, type);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0)
    {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 2);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    int len = min(max_len, (int)col->len);
    memcpy(otp, col->data, len);

    if (ptm) {
        col = records.at(0)->cols.at(1); 
        struct tm _tm;
        sscanf((char*)col->data, "%04d-%02d-%02d %02d:%02d:%02d", &_tm.tm_year, &_tm.tm_mon,
               &_tm.tm_mday, &_tm.tm_hour, &_tm.tm_min, &_tm.tm_sec);
        _tm.tm_year -= 1900;
        _tm.tm_mon --;
        _tm.tm_isdst=-1;
        *ptm = mktime(&_tm);
    }

    db->free_rows(&records);
    return len;
}

int OtpDB::get_sample_wb(const char *sn, int type, void *out)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, "SELECT wb FROM sample WHERE sn = '%s' AND type = %d",  sn, type);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0) {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    int len = min(4, (int)col->len);
    memcpy(out, col->data, len);
    return len;
}
int OtpDB::setting_get(const char *opt, char *val, int valsize)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, "SELECT val FROM setting WHERE opt = '%s'",  opt);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0) {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    int len = min(valsize-1, (int)col->len);
    memcpy(val, col->data, len);
    val[len] = '\0';
    return len;
}
int OtpDB::setting_set(const char *opt, const char *val)
{
    char sql[MAXTEXTLEN];

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    char tmp[0x100];
    if (setting_get(opt, tmp, sizeof(tmp)) < 0) {
        sprintf_s(sql, "INSERT INTO setting (opt, val) VALUES ('%s', '%s')", opt, val);
    } else {
        sprintf_s(sql, "UPDATE setting SET val = '%s' WHERE opt = '%s'", val, opt);
    }

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);

    return OTPDB_ERROR(ERR_NO);
}
int OtpDB::setting_get(const char *opt, int *val)
{
    int ret;
    char tmp[0x100];

    if ((ret = setting_get(opt, tmp, sizeof(tmp))) < 0) return ret;

    *val = atoi(tmp);
    return sizeof(*val);
}
int OtpDB::setting_get(const char *opt, double *val)
{
    int ret;
    char tmp[0x100];

    if ((ret = setting_get(opt, tmp, sizeof(tmp))) < 0) return ret;

    *val = atof(tmp);
    return sizeof(*val);
}
int OtpDB::setting_set(const char *opt, int val)
{
    char tmp[0x100];

    sprintf(tmp, "%d", val);
    return setting_set(opt, tmp);
}
int OtpDB::setting_set(const char *opt, double val)
{
    char tmp[0x100];

    sprintf(tmp, "%lf", val);
    return setting_set(opt, tmp);
}
//-----------------------------------------------------------------------------
otp_item *get_otp_item(int type, void *in, int len)
{
    uint8_t *_in = (uint8_t *)in;
    otp_item *otp;

    for (uint8_t *_in = (uint8_t *)in; 
          _in - (uint8_t *)in < len; 
          _in += (sizeof(otp_item)+otp->len)) {

        otp = (otp_item *)(_in);

        if (otp->magic != OTP_ITEM_MAGIC) break;

        if (otp->type == type) return otp; 
    }

    return NULL;
}

int OtpDB::get_otp_by_mid(int mid, void *out, int len)
{
    char sql[MAXTEXTLEN];
    sprintf_s(sql, 
              "SELECT otp_type, otp, update_time FROM otp_base WHERE module_id = %d",  
              mid);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0) {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}

    int idx = 0;
    for (auto iter = records.begin(); iter != records.end(); ++iter) {
        if (idx >= len) break;

        otp_item *otp = (otp_item *)((char *)out + idx); 
        DDM::DBCol *col = (*iter)->cols.at(0);

        otp->magic = OTP_ITEM_MAGIC;
        otp->type = int(col->data);

        col = (*iter)->cols.at(2); 
        struct tm _tm;
        sscanf((char*)col->data, "%04d-%02d-%02d %02d:%02d:%02d", &_tm.tm_year, &_tm.tm_mon,
               &_tm.tm_mday, &_tm.tm_hour, &_tm.tm_min, &_tm.tm_sec);
        _tm.tm_year -= 1900;
        _tm.tm_mon --;
        _tm.tm_isdst=-1;
        otp->tm = mktime(&_tm);

        col = (*iter)->cols.at(1);
        otp->len = col->len;

        int item_len = sizeof(otp_item)+otp->len;
        otp_item *otp_org = get_otp_item(otp->type, out, idx);
        if (otp_org && otp_org->len == otp->len && otp_org->tm < otp->tm ) {
            otp_org->tm = otp->tm;
            otp = otp_org;
            item_len = 0;
        }

        memcpy(otp->data, col->data, otp->len);

        idx += item_len;
    }

    db->free_rows(&records);
    return idx;
}
//-----------------------------------------------------------------------------
int OtpDB::UpdateOtpByType(int module_id, int type, char *otp, int len)
{
    char sql[MAXTEXTLEN];

    if (module_id < 0) return OTPDB_ERROR(ERR_OTPDB_MID_INVALID);

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    sprintf_s(sql, 
              "INSERT INTO otp_base (MODULE_ID, otp_type, otp) "
              "VALUES (%d, %d, ?)", module_id, type);

    DDM::DBCol colData;
    colData.len = len;
    colData.data = otp;
    vector<DDM::DBCol*> cols;
    cols.push_back(&colData);

    if (db->real_execute(sql, &cols) < 0)
        return OTPDB_ERROR(ERR_DB);

    return OTPDB_ERROR(ERR_NO);
}

int OtpDB::update_otp_datetime(int mid, int type)
{
    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    char sql[MAXTEXTLEN];
    sprintf_s(sql, "update otp_base set update_time = now() "
                   "where module_id = %d and otp_type = %d", 
              mid, type);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);

    return OTPDB_ERROR(ERR_NO);
}
//-----------------------------------------------------------------------------
int OtpDB::lock_otp_data(int mid)
{
    char sql[MAXTEXTLEN];

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    sprintf_s(sql,
              "UPDATE module_info SET LOCK_TIME=NOW() WHERE ID=%d", 
              mid);
    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);

    return OTPDB_ERROR(ERR_NO);
}
int OtpDB::get_otp_data_lock_time(int mid, time_t *time)
{
    char sql[MAXTEXTLEN];

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);
    sprintf_s(sql, 
              "SELECT LOCK_TIME FROM module_info WHERE ID=%d",mid);
    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0)
    {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    struct tm tm1;
    sscanf((char*)col->data, "%04d-%02d-%02d %02d:%02d:%02d", &tm1.tm_year, &tm1.tm_mon,
           &tm1.tm_mday, &tm1.tm_hour, &tm1.tm_min, &tm1.tm_sec);
    tm1.tm_year -= 1900;
    tm1.tm_mon --;
    tm1.tm_isdst=-1;
    *time = mktime(&tm1);

    db->free_rows(&records);
    return OTPDB_ERROR(ERR_NO);
}
//-----------------------------------------------------------------------------
int OtpDB::UpdateOtpBurnHistory(int module_id)
{
    char sql[MAXTEXTLEN];

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    sprintf_s(sql,
              "INSERT INTO otp_burn_history (MODULE_ID) VALUES (%d)", 
              module_id);
    if (db->execute(sql) < 0){
        return OTPDB_ERROR(ERR_DB);
    }

    return OTPDB_ERROR(ERR_NO);
}

//-----------------------------------------------------------------------------
int OtpDB::GetOtpBurnTime(int module_id, time_t *time)
{
    char sql[MAXTEXTLEN];

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);
    sprintf_s(sql, 
              "SELECT burn_date FROM otp_burn_history "
              "WHERE module_id = %d "
              "ORDER BY burn_date DESC LIMIT 1", 
              module_id);
    vector<DDM::DBRow*> records;
    if (db->execute_with_result(sql, &records) < 0)
    {
        db->free_rows(&records);
        return OTPDB_ERROR(ERR_DB);
    }

    if (records.size() == 0) {db->free_rows(&records);return OTPDB_ERROR(ERR_NOINFO);}
    assert(records.at(0)->cols.size() == 1);

    DDM::DBCol *col = records.at(0)->cols.at(0);
    struct tm tm1;
    sscanf((char*)col->data, "%04d-%02d-%02d %02d:%02d:%02d", &tm1.tm_year, &tm1.tm_mon,
           &tm1.tm_mday, &tm1.tm_hour, &tm1.tm_min, &tm1.tm_sec);
    tm1.tm_year -= 1900;
    tm1.tm_mon --;
    tm1.tm_isdst=-1;
    *time = mktime(&tm1);

    db->free_rows(&records);
    return OTPDB_ERROR(ERR_NO);
}

//-----------------------------------------------------------------------------
// Warnning: for test use.
int OtpDB::DBModuleInfoClear()
{
    char sql[MAXTEXTLEN] = "DELETE FROM module_info";

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);
    return OTPDB_ERROR(ERR_NO);
}
int OtpDB::DBOtpBaseClear()
{
    char sql[MAXTEXTLEN] = "DELETE FROM otp_base";

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);
    return OTPDB_ERROR(ERR_NO);
}
int OtpDB::DBOtpBurnHistoryClear()
{
    char sql[MAXTEXTLEN] = "DELETE FROM otp_burn_history";

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);
    return OTPDB_ERROR(ERR_NO);
}
int OtpDB::DBCaseInfoClear()
{
    char sql[MAXTEXTLEN] = "DELETE FROM case_info";

    DDM::Database *db = this->GetOtpDB();
    if (!db) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (!this->IsOtpDbConnected()) return OTPDB_ERROR(ERR_OTPDB_SETUP);

    if (db->execute(sql) < 0) return OTPDB_ERROR(ERR_DB);
    return OTPDB_ERROR(ERR_NO);
}

//-----------------------------------------------------------------------------
const char* OtpDB::OtpDBError()
{
	DDM::Database *db = GetOtpDB();

	switch (this->otpdb_errno) {
    case ERR_DB:
        return db->db_error();
    case ERR_OTPDB_SETUP:
        return "OTPDB setup ERROR, please check otpdb parameters!";
    case ERR_BURNED_BEFORE:
        return "This module has been burned before!";
    case ERR_NOINFO:
        return "Not got any information!";
    case ERR_OTPDB_MID_INVALID:
        return "MoudleID invalid!";
    case ERR_NO:
    default:
        return "No error occured!";
    }
}
}
//-----------------------------------------------------------------------------
